﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.ComponentModel;
using DirectZZT.Shared.Core;
using DirectZZT.Shared.World.Objects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using DirectZZT.Shared.Graphics;
#endregion

namespace DirectZZT.Shared.World
{
    /// <summary>
    /// Represents a DirectZZT world, which is a container for boards.
    /// </summary>
    [Serializable()]
    public class World : ISupportInitialize, IServiceProvider, IDisposable
    {
        #region Fields

        private List<Board> _boards;
        private Game _game;
        private ResourceManager<Texture2D> _textureManager;
        private SpriteBatch _spriteBatch;

        #endregion

        #region Properties

        /// <summary>
        /// The spritebatch for drawing.
        /// </summary>
        public SpriteBatch SpriteBatch
        {
            get { return _spriteBatch; }
        }
        /// <summary>
        /// Returns the Texture manager that contains the textures used by this world and its boards.
        /// </summary>
        public ResourceManager<Texture2D> TextureManager
        {
            get { return _textureManager; }
        }
        /// <summary>
        /// Gets/sets the matrix used to render the boards.
        /// </summary>
        public Matrix RenderMatrix { get; set; }

        /// <summary>
        /// Gets/sets the feature set of this World.
        /// </summary>
        public FeatureSet FeatureSet { get; set; }
        /// <summary>
        /// The author of the world.
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// Gets/sets the title/world name.
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// Gets the world data, containing information on how to play the world.
        /// </summary>
        public WorldData Data { get; private set; }
        /// <summary>
        /// Returns a read-only list containing all boards.
        /// Use the dedicated methods to modify the boards.
        /// </summary>
        public IList<Board> Boards
        {
            get { return _boards.AsReadOnly(); }
        }
        /// <summary>
        /// The BoardId of the board on which the world is started.
        /// </summary>
        public int InitialBoard { get; set; }

        /// <summary>
        /// Gets the board that is currently active.
        /// </summary>
        public Board CurrentBoard { get; private set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the ZZTWorld class.
        /// </summary>
        /// <param name="game">The game to use.</param>
        /// <exception cref="System.ArgumentNullException">Any of the arguments were <c>null</c>.</exception>
        public World(Game game)
        {
            if (game == null)
            {
                throw new ArgumentNullException("game");
            }

            _game = game;
            _spriteBatch = new SpriteBatch(_game.GraphicsDevice);

            // initialize texture manager
            _textureManager = new ResourceManager<Texture2D>();
            _textureManager.DefaultResource = _game.Content.Load<Texture2D>("Textures/PlayfieldObjects/NotFound");
        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds a new board for this world.
        /// </summary>
        /// <param name="board"></param>
        public void AddBoard(Board board)
        {
            lock (_boards)
            {
                board.World = this;
                _boards.Add(board);
                board.Initialize(this._game);
            }
        }

        /// <summary>
        /// Removes the board from the world.
        /// </summary>
        /// <param name="board"></param>
        public void RemoveBoard(Board board)
        {
            lock (_boards)
            {
                _boards.Remove(board);
            }
        }

        /// <summary>
        /// Updates the board and all objects on it.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            if (CurrentBoard != null)
            {
                CurrentBoard.Update(gameTime);
            }
        }

        /// <summary>
        /// Draws the board and all objects on it.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Draw(GameTime gameTime)
        {
            if (CurrentBoard != null)
            {
                _spriteBatch.Begin(SpriteSortMode.Texture, null, null, null, null, null, RenderMatrix);
                CurrentBoard.Draw(gameTime);
                _spriteBatch.End();
            }
        }

        /// <summary>
        /// Stops the world by stopping all boards.
        /// </summary>
        public void Stop()
        {
            lock (_boards)
            {
                for (int i = 0; i < _boards.Count; i++)
                {
                    _boards[i].Stop();
                }
                _boards.Clear();
            }

            DirectZZT.Shared.Scripting.ScriptDebugger.ClearScripts();
        }

        /// <summary>
        /// Sets the current active board to the given one.
        /// </summary>
        /// <param name="index">The index of the board to set active.</param>
        public void SetActiveBoard(int index)
        {
            Board destination = _boards.Find(b => b.BoardId == index);
            if (destination != null)
            {
                SetActiveBoard(destination);
            }
            else
            {
                Logging.GetLogging().LogFormat(LoggingMessageType.Warning, "Game", "", "Could not find board with index '{0}'!", index);
            }
        }

        /// <summary>
        /// Sets the current active board to the given one and does a transition.
        /// </summary>
        /// <param name="board">The board to set active.</param>
        public void SetActiveBoard(Board board)
        {
            Logging.GetLogging().LogFormat(LoggingMessageType.Info, "World", "Board", "Changing over to board '{0}' ({1})...", board.Title, board.BoardId);

            // if possible, leave the other board
            if (CurrentBoard != null)
            {
                Logging.GetLogging().LogFormat(LoggingMessageType.Info, "World", "Board", "Leaving current board '{0}' ({1})...", CurrentBoard.Title, CurrentBoard.BoardId);
                CurrentBoard.OnLeave();
            }
            // change over to the new board
            CurrentBoard = board;
            CurrentBoard.OnEnter();

            Logging.GetLogging().LogFormat(LoggingMessageType.Info, "World", "Board", "Now playing on board '{0}' ({1}).", CurrentBoard.Title, CurrentBoard.BoardId);
        }

        #endregion

        #region ISupportInitialize Members

        /// <summary>
        /// Signals the beginning of the initialization phase.
        /// </summary>
        public void BeginInit()
        {
            _boards = new List<Board>();
            Data = new WorldData();
        }

        /// <summary>
        /// Signals the end of the initialization phase.
        /// </summary>
        public void EndInit()
        {
            //lock (_boards)
            //{
            //    for (int i = 0; i < _boards.Count; i++)
            //    {
            //        _boards[i].Initialize(_game);
            //    }
            //}
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Disposes this world and all boards in this world.
        /// </summary>
        public void Dispose()
        {
            lock (_boards)
            {
                for (int i = 0; i < _boards.Count; i++)
                {
                    _boards[i].Dispose();
                }
            }

            _boards.Clear();
            _boards = null;

            _spriteBatch.Dispose();
            _spriteBatch = null;

            _textureManager.Dispose();
            _textureManager = null;

            //_effectManager.Dispose();
            //_effectManager = null;
        }

        #endregion

        #region IServiceProvider Members

        /// <summary>
        /// Returns the service instance of the given type.
        /// </summary>
        /// <param name="serviceType">The type of the service to return.</param>
        /// <returns>The service instance of the given type.
        /// -or- <c>null</c>, if no such service was registered.</returns>
        public object GetService(Type serviceType)
        {
            return _game.Services.GetService(serviceType);
        }

        #endregion
    }
}
